package com.google.dart.java2dart.util;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Copy of "org.eclipse.jdt.internal.corext.dom.Bindings" from JDT UI.
 */
public class Bindings {

  public static final String ARRAY_LENGTH_FIELD_BINDING_STRING = "(array type):length";//$NON-NLS-1$

  /**
   * @param overridden the overridden method
   * @param overridable the overriding method
   * @return returns <code>true</code> if the overriding method overrrides the overridden
   * @deprecated Need to review: Use {@link #isSubsignature(IMethodBinding, IMethodBinding)} if the
   *             two bindings are in the same hierarchy (directly overrides each other), or
   *             {@link #findMethodInHierarchy(ITypeBinding, String, ITypeBinding[])} else.
   */
  @Deprecated
  public static boolean areOverriddenMethods(IMethodBinding overridden, IMethodBinding overridable) {

    if (!overridden.getName().equals(overridable.getName())) {
      return false;
    }

    return areSubTypeCompatible(overridden, overridable);
  }

  /**
   * Note: this method is for debugging and testing purposes only. There are tests whose
   * pre-computed test results rely on the returned String's format.
   * 
   * @param binding the binding
   * @return a string representation of given binding
   * @see org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider
   */
  public static String asString(IBinding binding) {
    if (binding instanceof IMethodBinding) {
      return asString((IMethodBinding) binding);
    } else if (binding instanceof ITypeBinding) {
      return ((ITypeBinding) binding).getQualifiedName();
    } else if (binding instanceof IVariableBinding) {
      return asString((IVariableBinding) binding);
    }
    return binding.toString();
  }

  /**
   * @param candidates the candidates
   * @param overridable the overriding method
   * @return returns <code>true></code> if the overriding method overrides a candidate
   * @deprecated Need to review: Use {@link #isSubsignature(IMethodBinding, IMethodBinding)} if the
   *             two bindings are in the same hierarchy (directly overrides each other), or
   *             {@link #findMethodInHierarchy(ITypeBinding, String, ITypeBinding[])} else.
   */
  @Deprecated
  public static boolean containsSignatureEquivalentConstructor(IMethodBinding[] candidates,
      IMethodBinding overridable) {
    for (int index = 0; index < candidates.length; index++) {
      if (isSignatureEquivalentConstructor(candidates[index], overridable)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Checks if the declarations of two bindings are equals. Also works across binding environments.
   * 
   * @param b1 first binding, must not be <code>null</code>
   * @param b2 second binding, must not be <code>null</code>
   * @return boolean
   */
  public static boolean equalDeclarations(IBinding b1, IBinding b2) {
    if (b1.getKind() != b2.getKind()) {
      return false;
    }
    return getDeclaration(b1).isEqualTo(getDeclaration(b2));
  }

  /**
   * Checks if the two bindings are equals. Also works across binding environments.
   * 
   * @param b1 first binding treated as <code>this</code>. So it must not be <code>null</code>
   * @param b2 the second binding.
   * @return boolean
   */
  public static boolean equals(IBinding b1, IBinding b2) {
    return b1.isEqualTo(b2);
  }

  /**
   * Checks if the two arrays of bindings have the same length and their elements are equal. Uses
   * <code>Bindings.equals(IBinding, IBinding)</code> to compare.
   * 
   * @param b1 the first array of bindings. Must not be <code>null</code>.
   * @param b2 the second array of bindings.
   * @return boolean
   */
  public static boolean equals(IBinding[] b1, IBinding[] b2) {
    Assert.isNotNull(b1);
    if (b1 == b2) {
      return true;
    }
    if (b2 == null) {
      return false;
    }
    if (b1.length != b2.length) {
      return false;
    }
    for (int i = 0; i < b1.length; i++) {
      if (!Bindings.equals(b1[i], b2[i])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Finds the compilation unit where the type of the given <code>ITypeBinding</code> is defined,
   * using the class path defined by the given Java project. Returns <code>null</code> if no
   * compilation unit is found (e.g. type binding is from a binary type)
   * 
   * @param typeBinding the type binding to search for
   * @param project the project used as a scope
   * @return the compilation unit containing the type
   * @throws JavaModelException if an errors occurs in the Java model
   */
  public static ICompilationUnit findCompilationUnit(ITypeBinding typeBinding, IJavaProject project)
      throws JavaModelException {
    IJavaElement type = typeBinding.getJavaElement();
    if (type instanceof IType) {
      return ((IType) type).getCompilationUnit();
    } else {
      return null;
    }
  }

  /**
   * Finds the field specified by <code>fieldName</code> in the type hierarchy denoted by the given
   * type. Returns <code>null</code> if no such field exists. If the field is defined in more than
   * one super type only the first match is returned. First the super class is examined and then the
   * implemented interfaces.
   * 
   * @param type The type to search the field in
   * @param fieldName The name of the field to find
   * @return the variable binding representing the field
   */
  public static IVariableBinding findFieldInHierarchy(ITypeBinding type, String fieldName) {
    IVariableBinding field = findFieldInType(type, fieldName);
    if (field != null) {
      return field;
    }
    ITypeBinding superClass = type.getSuperclass();
    if (superClass != null) {
      field = findFieldInHierarchy(superClass, fieldName);
      if (field != null) {
        return field;
      }
    }
    ITypeBinding[] interfaces = type.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      field = findFieldInHierarchy(interfaces[i], fieldName);
      if (field != null) {
        return field;
      }
    }
    return null;
  }

  /**
   * Finds the field specified by <code>fieldName<code> in
   * the given <code>type</code>. Returns <code>null</code> if no such field exits.
   * 
   * @param type the type to search the field in
   * @param fieldName the field name
   * @return the binding representing the field or <code>null</code>
   */
  public static IVariableBinding findFieldInType(ITypeBinding type, String fieldName) {
    if (type.isPrimitive()) {
      return null;
    }
    IVariableBinding[] fields = type.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
      IVariableBinding field = fields[i];
      if (field.getName().equals(fieldName)) {
        return field;
      }
    }
    return null;
  }

  /**
   * Finds a method for the given <code>IMethodBinding</code>. Returns <code>null</code> if the type
   * doesn't contain a corresponding method.
   * 
   * @param method the method to find
   * @param type the type to look in
   * @return the corresponding IMethod or <code>null</code>
   * @throws JavaModelException if an error occurs in the Java model
   * @deprecated Use {@link #findMethodInHierarchy(ITypeBinding, String, String[])} or
   *             {@link JavaModelUtil}
   */
  @Deprecated
  public static IMethod findMethod(IMethodBinding method, IType type) throws JavaModelException {
    method = method.getMethodDeclaration();

    IMethod[] candidates = type.getMethods();
    for (int i = 0; i < candidates.length; i++) {
      IMethod candidate = candidates[i];
      if (candidate.getElementName().equals(method.getName()) && sameParameters(method, candidate)) {
        return candidate;
      }
    }
    return null;
  }

  /**
   * Finds the method specified by <code>methodName</code> and </code>parameters</code> in the type
   * hierarchy denoted by the given type. Returns <code>null</code> if no such method exists. If the
   * method is defined in more than one super type only the first match is returned. First the super
   * class is examined and then the implemented interfaces.
   * 
   * @param type The type to search the method in
   * @param methodName The name of the method to find
   * @param parameters The parameter types of the method to find. If <code>null</code> is passed,
   *          only the name is matched and parameters are ignored.
   * @return the method binding representing the method
   */
  public static IMethodBinding findMethodInHierarchy(ITypeBinding type, String methodName,
      ITypeBinding[] parameters) {
    IMethodBinding method = findMethodInType(type, methodName, parameters);
    if (method != null) {
      return method;
    }
    ITypeBinding superClass = type.getSuperclass();
    if (superClass != null) {
      method = findMethodInHierarchy(superClass, methodName, parameters);
      if (method != null) {
        return method;
      }
    }
    ITypeBinding[] interfaces = type.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      method = findMethodInHierarchy(interfaces[i], methodName, parameters);
      if (method != null) {
        return method;
      }
    }
    return null;
  }

  /**
   * Finds the method specified by <code>methodName</code> and </code>parameters</code> in the type
   * hierarchy denoted by the given type. Returns <code>null</code> if no such method exists. If the
   * method is defined in more than one super type only the first match is returned. First the super
   * class is examined and then the implemented interfaces.
   * 
   * @param type the type to search the method in
   * @param methodName The name of the method to find
   * @param parameters The parameter types of the method to find. If <code>null</code> is passed,
   *          only the name is matched and parameters are ignored.
   * @return the method binding representing the method
   */
  public static IMethodBinding findMethodInHierarchy(ITypeBinding type, String methodName,
      String[] parameters) {
    IMethodBinding method = findMethodInType(type, methodName, parameters);
    if (method != null) {
      return method;
    }
    ITypeBinding superClass = type.getSuperclass();
    if (superClass != null) {
      method = findMethodInHierarchy(superClass, methodName, parameters);
      if (method != null) {
        return method;
      }
    }
    ITypeBinding[] interfaces = type.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      method = findMethodInHierarchy(interfaces[i], methodName, parameters);
      if (method != null) {
        return method;
      }
    }
    return null;
  }

  /**
   * Finds the method specified by <code>methodName<code> and </code>parameters</code> in the given
   * <code>type</code>. Returns <code>null</code> if no such method exits.
   * 
   * @param type The type to search the method in
   * @param methodName The name of the method to find
   * @param parameters The parameter types of the method to find. If <code>null</code> is passed,
   *          only the name is matched and parameters are ignored.
   * @return the method binding representing the method
   */
  public static IMethodBinding findMethodInType(ITypeBinding type, String methodName,
      ITypeBinding[] parameters) {
    if (type.isPrimitive()) {
      return null;
    }
    IMethodBinding[] methods = type.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
      if (parameters == null) {
        if (methodName.equals(methods[i].getName())) {
          return methods[i];
        }
      } else {
        if (isEqualMethod(methods[i], methodName, parameters)) {
          return methods[i];
        }
      }
    }
    return null;
  }

  /**
   * Finds the method specified by <code>methodName<code> and </code>parameters</code> in the given
   * <code>type</code>. Returns <code>null</code> if no such method exits.
   * 
   * @param type The type to search the method in
   * @param methodName The name of the method to find
   * @param parameters The parameter types of the method to find. If <code>null</code> is passed,
   *          only the name is matched and parameters are ignored.
   * @return the method binding representing the method
   */
  public static IMethodBinding findMethodInType(ITypeBinding type, String methodName,
      String[] parameters) {
    if (type.isPrimitive()) {
      return null;
    }
    IMethodBinding[] methods = type.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
      if (parameters == null) {
        if (methodName.equals(methods[i].getName())) {
          return methods[i];
        }
      } else {
        if (isEqualMethod(methods[i], methodName, parameters)) {
          return methods[i];
        }
      }
    }
    return null;
  }

  /**
   * Finds the method that is overridden by the given method. The search is bottom-up, so this
   * returns the nearest defining/declaring method.
   * 
   * @param overriding overriding method
   * @param testVisibility If true the result is tested on visibility. Null is returned if the
   *          method is not visible.
   * @return the method binding representing the method
   */
  public static IMethodBinding findOverriddenMethod(IMethodBinding overriding,
      boolean testVisibility) {
    int modifiers = overriding.getModifiers();
    if (Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers) || overriding.isConstructor()) {
      return null;
    }

    ITypeBinding type = overriding.getDeclaringClass();
    if (type.getSuperclass() != null) {
      IMethodBinding res = findOverriddenMethodInHierarchy(type.getSuperclass(), overriding);
      if (res != null && !Modifier.isPrivate(res.getModifiers())) {
        if (!testVisibility
            || isVisibleInHierarchy(res, overriding.getDeclaringClass().getPackage())) {
          return res;
        }
      }
    }
    ITypeBinding[] interfaces = type.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      IMethodBinding res = findOverriddenMethodInHierarchy(interfaces[i], overriding);
      if (res != null) {
        return res; // methods from interfaces are always public and therefore visible
      }
    }
    return null;
  }

  /**
   * Finds a method in the hierarchy of <code>type</code> that is overridden by
   * </code>binding</code>. Returns <code>null</code> if no such method exists. If the method is
   * defined in more than one super type only the first match is returned. First the super class is
   * examined and then the implemented interfaces.
   * 
   * @param type The type to search the method in
   * @param binding The method that overrides
   * @return the method binding overridden the method
   */
  public static IMethodBinding findOverriddenMethodInHierarchy(ITypeBinding type,
      IMethodBinding binding) {
    IMethodBinding method = findOverriddenMethodInType(type, binding);
    if (method != null) {
      return method;
    }
    ITypeBinding superClass = type.getSuperclass();
    if (superClass != null) {
      method = findOverriddenMethodInHierarchy(superClass, binding);
      if (method != null) {
        return method;
      }
    }
    ITypeBinding[] interfaces = type.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      method = findOverriddenMethodInHierarchy(interfaces[i], binding);
      if (method != null) {
        return method;
      }
    }
    return null;
  }

  /**
   * Finds the method in the given <code>type</code> that is overridden by the specified
   * <code>method<code>.
   * Returns <code>null</code> if no such method exits.
   * 
   * @param type The type to search the method in
   * @param method The specified method that would override the result
   * @return the method binding of the method that is overridden by the specified
   *         <code>method<code>, or <code>null</code>
   */
  public static IMethodBinding findOverriddenMethodInType(ITypeBinding type, IMethodBinding method) {
    IMethodBinding[] methods = type.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
      if (isSubsignature(method, methods[i])) {
        return methods[i];
      }
    }
    return null;
  }

  /**
   * Finds a type binding for a given fully qualified type in the hierarchy of a type. Returns
   * <code>null</code> if no type binding is found.
   * 
   * @param hierarchyType the binding representing the hierarchy
   * @param fullyQualifiedTypeName the fully qualified name to search for
   * @return the type binding
   */
  public static ITypeBinding findTypeInHierarchy(ITypeBinding hierarchyType,
      String fullyQualifiedTypeName) {
    if (hierarchyType.isArray() || hierarchyType.isPrimitive()) {
      return null;
    }
    if (fullyQualifiedTypeName.equals(hierarchyType.getQualifiedName())) {
      return hierarchyType;
    }
    ITypeBinding superClass = hierarchyType.getSuperclass();
    if (superClass != null) {
      ITypeBinding res = findTypeInHierarchy(superClass, fullyQualifiedTypeName);
      if (res != null) {
        return res;
      }
    }
    ITypeBinding[] superInterfaces = hierarchyType.getInterfaces();
    for (int i = 0; i < superInterfaces.length; i++) {
      ITypeBinding res = findTypeInHierarchy(superInterfaces[i], fullyQualifiedTypeName);
      if (res != null) {
        return res;
      }
    }
    return null;
  }

  public static String[] getAllNameComponents(ITypeBinding type) {
    List<String> result = new ArrayList<String>(5);
    createName(type, true, result);
    return result.toArray(new String[result.size()]);
  }

  /**
   * Returns all super types (classes and interfaces) for the given type.
   * 
   * @param type The type to get the supertypes of.
   * @return all super types (excluding <code>type</code>)
   */
  public static ITypeBinding[] getAllSuperTypes(ITypeBinding type) {
    Set<ITypeBinding> result = new HashSet<ITypeBinding>();
    collectSuperTypes(type, result);
    result.remove(type);
    return result.toArray(new ITypeBinding[result.size()]);
  }

  /**
   * Returns the binding of the variable written in an Assignment.
   * 
   * @param assignment The assignment
   * @return The binding or <code>null</code> if no bindings are available.
   */
  public static IVariableBinding getAssignedVariable(Assignment assignment) {
    Expression leftHand = assignment.getLeftHandSide();
    switch (leftHand.getNodeType()) {
      case ASTNode.SIMPLE_NAME:
        return (IVariableBinding) ((SimpleName) leftHand).resolveBinding();
      case ASTNode.QUALIFIED_NAME:
        return (IVariableBinding) ((QualifiedName) leftHand).getName().resolveBinding();
      case ASTNode.FIELD_ACCESS:
        return ((FieldAccess) leftHand).resolveFieldBinding();
      case ASTNode.SUPER_FIELD_ACCESS:
        return ((SuperFieldAccess) leftHand).resolveFieldBinding();
      default:
        return null;
    }
  }

  /**
   * Returns the type binding of the node's enclosing type declaration.
   * 
   * @param node an AST node
   * @return the type binding of the node's parent type declaration, or <code>null</code>
   */
  public static ITypeBinding getBindingOfParentType(ASTNode node) {
    while (node != null) {
      if (node instanceof AbstractTypeDeclaration) {
        return ((AbstractTypeDeclaration) node).resolveBinding();
      } else if (node instanceof AnonymousClassDeclaration) {
        return ((AnonymousClassDeclaration) node).resolveBinding();
      }
      node = node.getParent();
    }
    return null;
  }

  /**
   * Returns the type binding of the node's type context or null if the node is inside an
   * annotation, type parameter, super type declaration, or Javadoc of a top level type. The result
   * of this method is equal to the result of {@link #getBindingOfParentType(ASTNode)} for nodes in
   * the type's body.
   * 
   * @param node an AST node
   * @return the type binding of the node's parent type context, or <code>null</code>
   */
  public static ITypeBinding getBindingOfParentTypeContext(ASTNode node) {
    StructuralPropertyDescriptor lastLocation = null;

    while (node != null) {
      if (node instanceof AbstractTypeDeclaration) {
        AbstractTypeDeclaration decl = (AbstractTypeDeclaration) node;
        if (lastLocation == decl.getBodyDeclarationsProperty()
            || lastLocation == decl.getJavadocProperty()) {
          return decl.resolveBinding();
        } else if (decl instanceof EnumDeclaration
            && lastLocation == EnumDeclaration.ENUM_CONSTANTS_PROPERTY) {
          return decl.resolveBinding();
        }
      } else if (node instanceof AnonymousClassDeclaration) {
        return ((AnonymousClassDeclaration) node).resolveBinding();
      }
      lastLocation = node.getLocationInParent();
      node = node.getParent();
    }
    return null;
  }

  /**
   * Returns the boxed type binding according to JLS3 5.1.7, or the original binding if the given
   * type is not a primitive type.
   * 
   * @param type a type binding
   * @param ast an AST to resolve the boxed type
   * @return the boxed type, or the original type if no boxed type found
   */
  public static ITypeBinding getBoxedTypeBinding(ITypeBinding type, AST ast) {
    if (!type.isPrimitive()) {
      return type;
    }
    String boxedTypeName = getBoxedTypeName(type.getName());
    if (boxedTypeName == null) {
      return type;
    }
    ITypeBinding boxed = ast.resolveWellKnownType(boxedTypeName);
    if (boxed == null) {
      return type;
    }
    return boxed;
  }

  public static IBinding getDeclaration(IBinding binding) {
    if (binding == null) {
      return null;
    }
    switch (binding.getKind()) {
      case IBinding.TYPE:
        return ((ITypeBinding) binding).getTypeDeclaration();
      case IBinding.VARIABLE:
        return ((IVariableBinding) binding).getVariableDeclaration();
      case IBinding.METHOD:
        return ((IMethodBinding) binding).getMethodDeclaration();
    }
    return binding;
  }

  /**
   * Returns the fully qualified name of the specified type binding.
   * <p>
   * If the binding resolves to a generic type, the fully qualified name of the raw type is
   * returned.
   * 
   * @param type the type binding to get its fully qualified name
   * @return the fully qualified name
   */
  public static String getFullyQualifiedName(ITypeBinding type) {
    String name = type.getQualifiedName();
    final int index = name.indexOf('<');
    if (index > 0) {
      name = name.substring(0, index);
    }
    return name;
  }

//  public static String getImportName(IBinding binding) {
//    ITypeBinding declaring = null;
//    switch (binding.getKind()) {
//      case IBinding.TYPE:
//        return getRawQualifiedName((ITypeBinding) binding);
//      case IBinding.PACKAGE:
//        return binding.getName() + ".*"; //$NON-NLS-1$
//      case IBinding.METHOD:
//        declaring = ((IMethodBinding) binding).getDeclaringClass();
//        break;
//      case IBinding.VARIABLE:
//        declaring = ((IVariableBinding) binding).getDeclaringClass();
//        if (declaring == null) {
//          return binding.getName(); // array.length
//        }
//
//        break;
//      default:
//        return binding.getName();
//    }
//    return JavaModelUtil.concatenateName(getRawQualifiedName(declaring), binding.getName());
//  }

  public static String[] getNameComponents(ITypeBinding type) {
    List<String> result = new ArrayList<String>(5);
    createName(type, false, result);
    return result.toArray(new String[result.size()]);
  }

  public static String getRawName(ITypeBinding binding) {
    String name = binding.getName();
    if (binding.isParameterizedType() || binding.isGenericType()) {
      int idx = name.indexOf('<');
      if (idx != -1) {
        return name.substring(0, idx);
      }
    }
    return name;
  }

  public static String getRawQualifiedName(ITypeBinding binding) {
    final String EMPTY = ""; //$NON-NLS-1$

    if (binding.isAnonymous() || binding.isLocal()) {
      return EMPTY;
    }

    if (binding.isPrimitive() || binding.isNullType() || binding.isTypeVariable()) {
      return binding.getName();
    }

    if (binding.isArray()) {
      String elementTypeQualifiedName = getRawQualifiedName(binding.getElementType());
      if (elementTypeQualifiedName.length() != 0) {
        StringBuffer stringBuffer = new StringBuffer(elementTypeQualifiedName);
        stringBuffer.append('[').append(']');
        return stringBuffer.toString();
      } else {
        return EMPTY;
      }
    }
    if (binding.isMember()) {
      String outerName = getRawQualifiedName(binding.getDeclaringClass());
      if (outerName.length() > 0) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(outerName);
        buffer.append('.');
        buffer.append(getRawName(binding));
        return buffer.toString();
      } else {
        return EMPTY;
      }

    } else if (binding.isTopLevel()) {
      IPackageBinding packageBinding = binding.getPackage();
      StringBuffer buffer = new StringBuffer();
      if (packageBinding != null && packageBinding.getName().length() > 0) {
        buffer.append(packageBinding.getName()).append('.');
      }
      buffer.append(getRawName(binding));
      return buffer.toString();
    }
    return EMPTY;
  }

  public static ITypeBinding getTopLevelType(ITypeBinding type) {
    ITypeBinding parent = type.getDeclaringClass();
    while (parent != null) {
      type = parent;
      parent = type.getDeclaringClass();
    }
    return type;
  }

  public static String getTypeQualifiedName(ITypeBinding type) {
    List<String> result = new ArrayList<String>(5);
    createName(type, false, result);

    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < result.size(); i++) {
      if (i > 0) {
        buffer.append('.');
      }
      buffer.append(result.get(i));
    }
    return buffer.toString();
  }

  /**
   * Returns the unboxed type binding according to JLS3 5.1.7, or the original binding if the given
   * type is not a boxed type.
   * 
   * @param type a type binding
   * @param ast an AST to resolve the unboxed type
   * @return the unboxed type, or the original type if no unboxed type found
   */
  public static ITypeBinding getUnboxedTypeBinding(ITypeBinding type, AST ast) {
    if (!type.isClass()) {
      return type;
    }
    String unboxedTypeName = getUnboxedTypeName(type.getQualifiedName());
    if (unboxedTypeName == null) {
      return type;
    }
    ITypeBinding unboxed = ast.resolveWellKnownType(unboxedTypeName);
    if (unboxed == null) {
      return type;
    }
    return unboxed;
  }

  public static int hashCode(IBinding binding) {
    Assert.isNotNull(binding);
    String key = binding.getKey();
    if (key == null) {
      return binding.hashCode();
    }
    return key.hashCode();
  }

  /**
   * Tests if the given node is a declaration, not a instance of a generic type, method or field.
   * Declarations can be found in AST with CompilationUnit.findDeclaringNode
   * 
   * @param binding binding to test
   * @return returns <code>true</code> if the binding is a declaration binding
   */
  public static boolean isDeclarationBinding(IBinding binding) {
    switch (binding.getKind()) {
      case IBinding.TYPE:
        return ((ITypeBinding) binding).getTypeDeclaration() == binding;
      case IBinding.VARIABLE:
        return ((IVariableBinding) binding).getVariableDeclaration() == binding;
      case IBinding.METHOD:
        return ((IMethodBinding) binding).getMethodDeclaration() == binding;
    }
    return true;
  }

  /**
   * Tests whether the two methods are erasure-equivalent.
   * 
   * @param method the first method
   * @param methodName the name of the second method
   * @param parameters the parameters of the second parameters
   * @return return <code>true</code> if the two bindings are equal
   * @deprecated use {@link #isSubsignature(IMethodBinding, IMethodBinding)}
   */
  //TODO: rename to isErasureEquivalentMethod and change to two IMethodBinding parameters
  @Deprecated
  public static boolean isEqualMethod(IMethodBinding method, String methodName,
      ITypeBinding[] parameters) {
    if (!method.getName().equals(methodName)) {
      return false;
    }

    ITypeBinding[] methodParameters = method.getParameterTypes();
    if (methodParameters.length != parameters.length) {
      return false;
    }
    for (int i = 0; i < parameters.length; i++) {
      if (!equals(methodParameters[i].getErasure(), parameters[i].getErasure())) {
        return false;
      }
    }
    //Can't use this fix, since some clients assume that this method tests erasure equivalence:
//		if (method.getTypeParameters().length == 0) {
//			//a method without type parameters cannot be overridden by one that declares type parameters -> can be exact here
//			for (int i= 0; i < parameters.length; i++) {
//				if ( ! (equals(methodParameters[i], parameters[i])
//						|| equals(methodParameters[i].getErasure(), parameters[i]))) // subsignature
//					return false;
//			}
//		} else {
//			//this will find all overridden methods, but may generate false positives in some cases:
//			for (int i= 0; i < parameters.length; i++) {
//				if (!equals(methodParameters[i].getErasure(), parameters[i].getErasure()))
//					return false;
//			}
//		}
    return true;
  }

  /**
   * Checks whether a method with the given name and parameter types is a subsignature of the given
   * method binding.
   * 
   * @param method a method
   * @param methodName method name to match
   * @param parameters the parameter types of the method to find. If <code>null</code> is passed,
   *          only the name is matched and parameters are ignored.
   * @return <code>true</code> iff the method m1 (with name <code>methodName</code> and method
   *         parameters <code>parameters</code>) is a subsignature of the method <code>m2</code>.
   *         Accessibility and return types are not taken into account.
   */
  public static boolean isEqualMethod(IMethodBinding method, String methodName, String[] parameters) {
    if (!method.getName().equals(methodName)) {
      return false;
    }

    ITypeBinding[] methodParameters = method.getParameterTypes();
    if (methodParameters.length != parameters.length) {
      return false;
    }
    String first, second;
    int index;
    for (int i = 0; i < parameters.length; i++) {
      first = parameters[i];
      index = first.indexOf('<');
      if (index > 0) {
        first = first.substring(0, index);
      }
      second = methodParameters[i].getErasure().getQualifiedName();
      index = second.indexOf('<');
      if (index > 0) {
        second = second.substring(0, index);
      }
      if (!first.equals(second)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Checks whether the passed type binding is a runtime exception.
   * 
   * @param thrownException the type binding
   * @return <code>true</code> if the passed type binding is a runtime exception; otherwise
   *         <code>false</code> is returned
   */
  public static boolean isRuntimeException(ITypeBinding thrownException) {
    if (thrownException == null || thrownException.isPrimitive() || thrownException.isArray()) {
      return false;
    }
    return findTypeInHierarchy(thrownException, "java.lang.RuntimeException") != null; //$NON-NLS-1$
  }

  /**
   * @param overriding overriding method (m1)
   * @param overridden overridden method (m2)
   * @return <code>true</code> iff the method <code>m1</code> is a subsignature of the method
   *         <code>m2</code>. This is one of the requirements for m1 to override m2. Accessibility
   *         and return types are not taken into account. Note that subsignature is <em>not</em>
   *         symmetric!
   */
  public static boolean isSubsignature(IMethodBinding overriding, IMethodBinding overridden) {
    //TODO: use IMethodBinding#isSubsignature(..) once it is tested and fixed (only erasure of m1's parameter types, considering type variable counts, doing type variable substitution
    if (!overriding.getName().equals(overridden.getName())) {
      return false;
    }

    ITypeBinding[] m1Params = overriding.getParameterTypes();
    ITypeBinding[] m2Params = overridden.getParameterTypes();
    if (m1Params.length != m2Params.length) {
      return false;
    }

    ITypeBinding[] m1TypeParams = overriding.getTypeParameters();
    ITypeBinding[] m2TypeParams = overridden.getTypeParameters();
    if (m1TypeParams.length != m2TypeParams.length && m1TypeParams.length != 0) {
      return false;
    }

    //m1TypeParameters.length == (m2TypeParameters.length || 0)
    if (m2TypeParams.length != 0) {
      //Note: this branch does not 100% adhere to the spec and may report some false positives.
      // Full compliance would require major duplication of compiler code.

      //Compare type parameter bounds:
      for (int i = 0; i < m1TypeParams.length; i++) {
        // loop over m1TypeParams, which is either empty, or equally long as m2TypeParams
        Set<ITypeBinding> m1Bounds = getTypeBoundsForSubsignature(m1TypeParams[i]);
        Set<ITypeBinding> m2Bounds = getTypeBoundsForSubsignature(m2TypeParams[i]);
        if (!m1Bounds.equals(m2Bounds)) {
          return false;
        }
      }
      //Compare parameter types:
      if (equals(m2Params, m1Params)) {
        return true;
      }
      for (int i = 0; i < m1Params.length; i++) {
        ITypeBinding m1Param = m1Params[i];
        ITypeBinding m2Param = m2Params[i];
        if (containsTypeVariables(m1Param) || m1Param.isRawType()) {
          m1Param = m1Param.getErasure(); // try to achieve effect of "rename type variables"
        }
        if (!(equals(m1Param, m2Param) || equals(m1Param, m2Param.getErasure()))) {
          return false;
        }
      }
      return true;

    } else {
      // m1TypeParams.length == m2TypeParams.length == 0
      if (equals(m1Params, m2Params)) {
        return true;
      }
      for (int i = 0; i < m1Params.length; i++) {
        ITypeBinding m1Param = m1Params[i];
        ITypeBinding m2Param = m2Params[i];
        if (m1Param.isRawType()) {
          m1Param = m1Param.getTypeDeclaration();
        }
        if (!(equals(m1Param, m2Param) || equals(m1Param, m2Param.getErasure()))) {
          return false;
        }
      }
      return true;
    }
  }

  /**
   * Returns <code>true</code> if the given type is a super type of a candidate. <code>true</code>
   * is returned if the two type bindings are identical.
   * <p>
   * <b>Warning:</b> With the addition of generics, this method is valid in less cases than before.
   * Consider using {@link TypeRules#canAssign(ITypeBinding, ITypeBinding)} if you're dealing with
   * types of variables. The classical notion of supertypes only makes sense if you really need to
   * walk the type hierarchy but don't need to play the assignment rules.
   * </p>
   * 
   * @param possibleSuperType the type to inspect
   * @param type the type whose super types are looked at
   * @return <code>true</code> iff <code>possibleSuperType</code> is a super type of
   *         <code>type</code> or is equal to it
   */
  public static boolean isSuperType(ITypeBinding possibleSuperType, ITypeBinding type) {
    return isSuperType(possibleSuperType, type, true);
  }

  /**
   * Returns <code>true</code> if the given type is a super type of a candidate. <code>true</code>
   * is returned if the two type bindings are identical (TODO)
   * 
   * @param possibleSuperType the type to inspect
   * @param type the type whose super types are looked at
   * @param considerTypeArguments if <code>true</code>, consider type arguments of <code>type</code>
   * @return <code>true</code> iff <code>possibleSuperType</code> is a super type of
   *         <code>type</code> or is equal to it
   */
  public static boolean isSuperType(ITypeBinding possibleSuperType, ITypeBinding type,
      boolean considerTypeArguments) {
    if (type.isArray() || type.isPrimitive()) {
      return false;
    }
    if (!considerTypeArguments) {
      type = type.getTypeDeclaration();
    }
    if (Bindings.equals(type, possibleSuperType)) {
      return true;
    }
    ITypeBinding superClass = type.getSuperclass();
    if (superClass != null) {
      if (isSuperType(possibleSuperType, superClass, considerTypeArguments)) {
        return true;
      }
    }

    if (possibleSuperType.isInterface()) {
      ITypeBinding[] superInterfaces = type.getInterfaces();
      for (int i = 0; i < superInterfaces.length; i++) {
        if (isSuperType(possibleSuperType, superInterfaces[i], considerTypeArguments)) {
          return true;
        }
      }
    }
    return false;
  }

  public static boolean isVisibleInHierarchy(IMethodBinding member, IPackageBinding pack) {
    int otherflags = member.getModifiers();
    ITypeBinding declaringType = member.getDeclaringClass();
    if (Modifier.isPublic(otherflags) || Modifier.isProtected(otherflags) || declaringType != null
        && declaringType.isInterface()) {
      return true;
    } else if (Modifier.isPrivate(otherflags)) {
      return false;
    }
    return declaringType != null && pack == declaringType.getPackage();
  }

  public static boolean isVoidType(ITypeBinding binding) {
    return "void".equals(binding.getName()); //$NON-NLS-1$
  }

  //---- Helper methods to convert a method ---------------------------------------------

  /**
   * Normalizes the binding so that it can be used as a type inside a declaration (e.g. variable
   * declaration, method return type, parameter type, ...). For null bindings, java.lang.Object is
   * returned. For void bindings, <code>null</code> is returned.
   * 
   * @param binding binding to normalize
   * @param ast current AST
   * @return the normalized type to be used in declarations, or <code>null</code>
   */
  public static ITypeBinding normalizeForDeclarationUse(ITypeBinding binding, AST ast) {
    if (binding.isNullType()) {
      return ast.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
    }
    if (binding.isPrimitive()) {
      return binding;
    }
    binding = normalizeTypeBinding(binding);
    if (binding == null || !binding.isWildcardType()) {
      return binding;
    }
    if (binding.isUpperbound()) {
      return binding.getBound();
    } else {
      return ast.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
    }
  }

  /**
   * Normalizes a type binding received from an expression to a type binding that can be used inside
   * a declaration signature, but <em>not</em> as type of a declaration (use
   * {@link #normalizeForDeclarationUse(ITypeBinding, AST)} for that).
   * <p>
   * Anonymous types are normalized to the super class or interface. For null or void bindings,
   * <code>null</code> is returned.
   * </p>
   * 
   * @param binding the binding to normalize
   * @return the normalized binding, can be <code>null</code>
   * @see #normalizeForDeclarationUse(ITypeBinding, AST)
   */
  public static ITypeBinding normalizeTypeBinding(ITypeBinding binding) {
    if (binding != null && !binding.isNullType() && !isVoidType(binding)) {
      if (binding.isAnonymous()) {
        ITypeBinding[] baseBindings = binding.getInterfaces();
        if (baseBindings.length > 0) {
          return baseBindings[0];
        }
        return binding.getSuperclass();
      }
      if (binding.isCapture()) {
        return binding.getWildcard();
      }
      return binding;
    }
    return null;
  }

  /**
   * Resolve the binding (<em>not</em> the type binding) for the expression or a nested expression
   * (e.g. nested in parentheses, cast, ...).
   * 
   * @param expression an expression node
   * @param goIntoCast iff <code>true</code>, go into a CastExpression's expression to resolve
   * @return the expression binding, or <code>null</code> if the expression has no binding or the
   *         binding could not be resolved
   * @see StubUtility#getVariableNameSuggestions(int, IJavaProject, ITypeBinding, Expression,
   *      java.util.Collection)
   * @since 3.5
   */
  public static IBinding resolveExpressionBinding(Expression expression, boolean goIntoCast) {
    //TODO: search for callers of resolve*Binding() methods and replace with call to this method

    // similar to StubUtility#getVariableNameSuggestions(int, IJavaProject, ITypeBinding, Expression, Collection)
    switch (expression.getNodeType()) {
      case ASTNode.SIMPLE_NAME:
      case ASTNode.QUALIFIED_NAME:
        return ((Name) expression).resolveBinding();

      case ASTNode.FIELD_ACCESS:
        return ((FieldAccess) expression).resolveFieldBinding();
      case ASTNode.SUPER_FIELD_ACCESS:
        return ((SuperFieldAccess) expression).resolveFieldBinding();

      case ASTNode.METHOD_INVOCATION:
        return ((MethodInvocation) expression).resolveMethodBinding();
      case ASTNode.SUPER_METHOD_INVOCATION:
        return ((SuperMethodInvocation) expression).resolveMethodBinding();
      case ASTNode.CLASS_INSTANCE_CREATION:
        return ((ClassInstanceCreation) expression).resolveConstructorBinding();

      case ASTNode.MARKER_ANNOTATION:
      case ASTNode.SINGLE_MEMBER_ANNOTATION:
      case ASTNode.NORMAL_ANNOTATION:
        return ((Annotation) expression).resolveAnnotationBinding();

      case ASTNode.ARRAY_ACCESS:
        return resolveExpressionBinding(((ArrayAccess) expression).getArray(), goIntoCast);
      case ASTNode.CAST_EXPRESSION:
        if (goIntoCast) {
          return resolveExpressionBinding(((CastExpression) expression).getExpression(), true);
        } else {
          return null;
        }
      case ASTNode.PARENTHESIZED_EXPRESSION:
        return resolveExpressionBinding(
            ((ParenthesizedExpression) expression).getExpression(),
            goIntoCast);
      case ASTNode.PREFIX_EXPRESSION:
        return resolveExpressionBinding(((PrefixExpression) expression).getOperand(), goIntoCast);
      case ASTNode.POSTFIX_EXPRESSION:
        return resolveExpressionBinding(((PostfixExpression) expression).getOperand(), goIntoCast);
      default:
        return null;
    }
  }

//  /**
//   * Method to visit a type hierarchy defined by a given type.
//   * 
//   * @param type the type which hierarchy is to be visited
//   * @param visitor the visitor
//   * @return <code>false</code> if the visiting got interrupted
//   */
//  public static boolean visitHierarchy(ITypeBinding type, TypeBindingVisitor visitor) {
//    boolean result = visitSuperclasses(type, visitor);
//    if (result) {
//      result = visitInterfaces(type, visitor);
//    }
//    return result;
//  }
//
//  /**
//   * Method to visit a interface hierarchy defined by a given type.
//   * 
//   * @param type the type which interface hierarchy is to be visited
//   * @param visitor the visitor
//   * @return <code>false</code> if the visiting got interrupted
//   */
//  public static boolean visitInterfaces(ITypeBinding type, TypeBindingVisitor visitor) {
//    ITypeBinding[] interfaces = type.getInterfaces();
//    for (int i = 0; i < interfaces.length; i++) {
//      if (!visitor.visit(interfaces[i])) {
//        return false;
//      }
//    }
//    return true;
//  }
//
//  /**
//   * Method to visit a super class hierarchy defined by a given type.
//   * 
//   * @param type the type which super class hierarchy is to be visited
//   * @param visitor the visitor
//   * @return <code>false</code> if the visiting got interrupted
//   */
//  public static boolean visitSuperclasses(ITypeBinding type, TypeBindingVisitor visitor) {
//    while ((type = type.getSuperclass()) != null) {
//      if (!visitor.visit(type)) {
//        return false;
//      }
//    }
//    return true;
//  }

  static boolean containsTypeVariables(ITypeBinding type) {
    if (type.isTypeVariable()) {
      return true;
    }
    if (type.isArray()) {
      return containsTypeVariables(type.getElementType());
    }
    if (type.isCapture()) {
      return containsTypeVariables(type.getWildcard());
    }
    if (type.isParameterizedType()) {
      return containsTypeVariables(type.getTypeArguments());
    }
    if (type.isTypeVariable()) {
      return containsTypeVariables(type.getTypeBounds());
    }
    if (type.isWildcardType() && type.getBound() != null) {
      return containsTypeVariables(type.getBound());
    }
    return false;
  }

  private static boolean areSubTypeCompatible(IMethodBinding overridden, IMethodBinding overridable) {

    if (overridden.getParameterTypes().length != overridable.getParameterTypes().length) {
      return false;
    }

    ITypeBinding overriddenReturn = overridden.getReturnType();
    ITypeBinding overridableReturn = overridable.getReturnType();
    if (overriddenReturn == null || overridableReturn == null) {
      return false;
    }

    if (!overriddenReturn.getErasure().isSubTypeCompatible(overridableReturn.getErasure())) {
      return false;
    }

    ITypeBinding[] overriddenTypes = overridden.getParameterTypes();
    ITypeBinding[] overridableTypes = overridable.getParameterTypes();
    Assert.isTrue(overriddenTypes.length == overridableTypes.length);
    for (int index = 0; index < overriddenTypes.length; index++) {
      final ITypeBinding overridableErasure = overridableTypes[index].getErasure();
      final ITypeBinding overriddenErasure = overriddenTypes[index].getErasure();
      if (!overridableErasure.isSubTypeCompatible(overriddenErasure)
          || !overridableErasure.getKey().equals(overriddenErasure.getKey())) {
        return false;
      }
    }
    ITypeBinding[] overriddenExceptions = overridden.getExceptionTypes();
    ITypeBinding[] overridableExceptions = overridable.getExceptionTypes();
    boolean checked = false;
    for (int index = 0; index < overriddenExceptions.length; index++) {
      checked = false;
      for (int offset = 0; offset < overridableExceptions.length; offset++) {
        if (overriddenExceptions[index].isSubTypeCompatible(overridableExceptions[offset])) {
          checked = true;
        }
      }
      if (!checked) {
        return false;
      }
    }
    return true;
  }

  private static String asString(IMethodBinding method) {
    StringBuffer result = new StringBuffer();
    result.append(method.getDeclaringClass().getName());
    result.append(':');
    result.append(method.getName());
    result.append('(');
    ITypeBinding[] parameters = method.getParameterTypes();
    int lastComma = parameters.length - 1;
    for (int i = 0; i < parameters.length; i++) {
      ITypeBinding parameter = parameters[i];
      result.append(parameter.getName());
      if (i < lastComma) {
        result.append(", "); //$NON-NLS-1$
      }
    }
    result.append(')');
    return result.toString();
  }

  private static String asString(IVariableBinding variableBinding) {
    if (!variableBinding.isField()) {
      return variableBinding.toString();
    }
    if (variableBinding.getDeclaringClass() == null) {
      Assert.isTrue(variableBinding.getName().equals("length"));//$NON-NLS-1$
      return ARRAY_LENGTH_FIELD_BINDING_STRING;
    }
    StringBuffer result = new StringBuffer();
    result.append(variableBinding.getDeclaringClass().getName());
    result.append(':');
    result.append(variableBinding.getName());
    return result.toString();
  }

  private static void collectSuperTypes(ITypeBinding curr, Set<ITypeBinding> collection) {
    if (collection.add(curr)) {
      ITypeBinding[] interfaces = curr.getInterfaces();
      for (int i = 0; i < interfaces.length; i++) {
        collectSuperTypes(interfaces[i], collection);
      }
      ITypeBinding superClass = curr.getSuperclass();
      if (superClass != null) {
        collectSuperTypes(superClass, collection);
      }
    }
  }

  private static boolean containsTypeVariables(ITypeBinding[] types) {
    for (int i = 0; i < types.length; i++) {
      if (containsTypeVariables(types[i])) {
        return true;
      }
    }
    return false;
  }

  private static void createName(ITypeBinding type, boolean includePackage, List<String> list) {
    ITypeBinding baseType = type;
    if (type.isArray()) {
      baseType = type.getElementType();
    }
    if (!baseType.isPrimitive() && !baseType.isNullType()) {
      ITypeBinding declaringType = baseType.getDeclaringClass();
      if (declaringType != null) {
        createName(declaringType, includePackage, list);
      } else if (includePackage && !baseType.getPackage().isUnnamed()) {
        String[] components = baseType.getPackage().getNameComponents();
        for (int i = 0; i < components.length; i++) {
          list.add(components[i]);
        }
      }
    }
    if (!baseType.isAnonymous()) {
      list.add(type.getName());
    } else {
      list.add("$local$"); //$NON-NLS-1$
    }
  }

  private static String getBoxedTypeName(String primitiveName) {
    if ("long".equals(primitiveName)) {
      return "java.lang.Long"; //$NON-NLS-1$
    } else if ("int".equals(primitiveName)) {
      return "java.lang.Integer"; //$NON-NLS-1$
    } else if ("short".equals(primitiveName)) {
      return "java.lang.Short"; //$NON-NLS-1$
    } else if ("char".equals(primitiveName)) {
      return "java.lang.Character"; //$NON-NLS-1$
    } else if ("byte".equals(primitiveName)) {
      return "java.lang.Byte"; //$NON-NLS-1$
    } else if ("boolean".equals(primitiveName)) {
      return "java.lang.Boolean"; //$NON-NLS-1$
    } else if ("float".equals(primitiveName)) {
      return "java.lang.Float"; //$NON-NLS-1$
    } else if ("double".equals(primitiveName)) {
      return "java.lang.Double"; //$NON-NLS-1$
    } else {
      return null;
    }
  }

  private static Set<ITypeBinding> getTypeBoundsForSubsignature(ITypeBinding typeParameter) {
    ITypeBinding[] typeBounds = typeParameter.getTypeBounds();
    int count = typeBounds.length;
    if (count == 0) {
      return Collections.emptySet();
    }

    Set<ITypeBinding> result = new HashSet<ITypeBinding>(typeBounds.length);
    for (int i = 0; i < typeBounds.length; i++) {
      ITypeBinding bound = typeBounds[i];
      if ("java.lang.Object".equals(typeBounds[0].getQualifiedName())) {
        continue;
      } else if (containsTypeVariables(bound)) {
        result.add(bound.getErasure()); // try to achieve effect of "rename type variables"
      } else if (bound.isRawType()) {
        result.add(bound.getTypeDeclaration());
      } else {
        result.add(bound);
      }
    }
    return result;
  }

  private static String getUnboxedTypeName(String boxedName) {
    if ("java.lang.Long".equals(boxedName)) {
      return "long"; //$NON-NLS-1$
    } else if ("java.lang.Integer".equals(boxedName)) {
      return "int"; //$NON-NLS-1$
    } else if ("java.lang.Short".equals(boxedName)) {
      return "short"; //$NON-NLS-1$
    } else if ("java.lang.Character".equals(boxedName)) {
      return "char"; //$NON-NLS-1$
    } else if ("java.lang.Byte".equals(boxedName)) {
      return "byte"; //$NON-NLS-1$
    } else if ("java.lang.Boolean".equals(boxedName)) {
      return "boolean"; //$NON-NLS-1$
    } else if ("java.lang.Float".equals(boxedName)) {
      return "float"; //$NON-NLS-1$
    } else if ("java.lang.Double".equals(boxedName)) {
      return "double"; //$NON-NLS-1$
    } else {
      return null;
    }
  }

  private static boolean isSignatureEquivalentConstructor(IMethodBinding overridden,
      IMethodBinding overridable) {

    if (!overridden.isConstructor() || !overridable.isConstructor()) {
      return false;
    }

    if (overridden.isDefaultConstructor()) {
      return false;
    }

    return areSubTypeCompatible(overridden, overridable);
  }

  private static boolean sameParameter(ITypeBinding type, String candidate, IType scope)
      throws JavaModelException {
    if (type.getDimensions() != Signature.getArrayCount(candidate)) {
      return false;
    }

    // Normalizes types
    if (type.isArray()) {
      type = type.getElementType();
    }
    candidate = Signature.getElementType(candidate);

    if (Signature.getTypeSignatureKind(candidate) == Signature.BASE_TYPE_SIGNATURE != type.isPrimitive()) {
      return false;
    }

    if (type.isPrimitive() || type.isTypeVariable()) {
      return type.getName().equals(Signature.toString(candidate));
    } else {
      // normalize (quick hack until binding.getJavaElement works)
      candidate = Signature.getTypeErasure(candidate);
      type = type.getErasure();

      if (candidate.charAt(Signature.getArrayCount(candidate)) == Signature.C_RESOLVED) {
        return Signature.toString(candidate).equals(Bindings.getFullyQualifiedName(type));
      } else {
        String[][] qualifiedCandidates = scope.resolveType(Signature.toString(candidate));
        if (qualifiedCandidates == null || qualifiedCandidates.length == 0) {
          return false;
        }
        String packageName = type.getPackage().isUnnamed() ? "" : type.getPackage().getName(); //$NON-NLS-1$
        String typeName = getTypeQualifiedName(type);
        for (int i = 0; i < qualifiedCandidates.length; i++) {
          String[] qualifiedCandidate = qualifiedCandidates[i];
          if (qualifiedCandidate[0].equals(packageName) && qualifiedCandidate[1].equals(typeName)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  private static boolean sameParameters(IMethodBinding method, IMethod candidate)
      throws JavaModelException {
    ITypeBinding[] methodParamters = method.getParameterTypes();
    String[] candidateParameters = candidate.getParameterTypes();
    if (methodParamters.length != candidateParameters.length) {
      return false;
    }
    IType scope = candidate.getDeclaringType();
    for (int i = 0; i < methodParamters.length; i++) {
      ITypeBinding methodParameter = methodParamters[i];
      String candidateParameter = candidateParameters[i];
      if (!sameParameter(methodParameter, candidateParameter, scope)) {
        return false;
      }
    }
    return true;
  }

  private Bindings() {
    // No instance
  }

}
